feat(terraform): add the Azure onyx composition and README - #14105
Conversation
Greptile SummaryThe PR adds an Azure Terraform composition that wires networking, storage, PostgreSQL, Redis, AKS, and WAF modules together with tiered sizing and deployment guidance.
Confidence Score: 4/5The PR is not yet safe to merge because disabling Terraform-owned service-account creation can also suppress the namespace required by the documented Helm installation. The composition exposes independent namespace and service-account ownership flags, but the AKS module couples namespace creation to service-account creation; a supported chart-owned service-account configuration therefore leaves Helm targeting a namespace that was never created. Files Needing Attention: deployment/terraform/modules/azure/onyx/variables.tf, deployment/terraform/modules/azure/onyx/main.tf Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
Root[Root Terraform module] --> Onyx[Azure Onyx composition]
Onyx --> VNet[VNet and subnets]
Onyx --> Storage[Blob file store]
Onyx --> Postgres[PostgreSQL Flexible Server]
Onyx --> Redis[Azure Cache for Redis]
Onyx --> AKS[AKS cluster]
Onyx --> WAF[WAF policy]
VNet --> AKS
VNet --> Postgres
VNet --> Redis
Storage --> AKS
AKS --> Namespace[onyx namespace]
Namespace --> ServiceAccount[Federated service account]
ServiceAccount --> Helm[Onyx Helm workloads]
Prompt To Fix All With AI### Issue 1
deployment/terraform/modules/azure/onyx/variables.tf:396-399
**Service-account opt-out drops namespace**
When a caller sets `create_workload_service_account=false` so Helm can create the service account while leaving `create_workload_namespace=true`, the AKS module also skips namespace creation. The documented Helm command does not pass `--create-namespace`, so installation fails because the `onyx` namespace does not exist.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (4): Last reviewed commit: "feat(terraform): add the Azure onyx comp..." | Re-trigger Greptile |
There was a problem hiding this comment.
6 issues found across 6 files
Confidence score: 2/5
deployment/terraform/modules/azure/README.mddoes not configure file-store pods with the federatedonyx-workload-accessservice account and workload-identity annotation, soDefaultAzureCredentialcan fail at runtime; update the chart pod templates before relying on federated access.deployment/terraform/modules/azure/onyx/main.tfenables PostgreSQL password authentication whenpostgres_passwordis omitted, causing Azure apply failures; require a password or disable password authentication.- The default AKS settings in
deployment/terraform/modules/azure/variables.tfexpose the API server publicly without authorized IP ranges, creating a significant security exposure; default to a private or restricted API server configuration. - Existing-network configurations in
deployment/terraform/modules/azure/onyx/main.tfcan silently omit flow logs and override an existing NAT gateway’s stable egress behavior, while the README example also references an undefinedvar.postgres_password; validate these combinations and correct the example before documenting the path.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="deployment/terraform/modules/azure/onyx/main.tf">
<violation number="1" location="deployment/terraform/modules/azure/onyx/main.tf:146">
P2: When `create_virtual_network = false` and `enable_flow_logs = true`, this creates a dedicated account but no flow-log resource, so logging is silently absent. Reject this combination or configure flow logs for the supplied VNet instead of creating an unused account.</violation>
<violation number="2" location="deployment/terraform/modules/azure/onyx/main.tf:189">
P1: When callers omit `postgres_password`, the composition plans with password authentication enabled but Azure rejects the server at apply. Require a non-null password or disable password authentication before invoking `module.postgres`.</violation>
<violation number="3" location="deployment/terraform/modules/azure/onyx/main.tf:240">
P2: When a supplied AKS subnet already has a NAT gateway, `create_virtual_network = false` still forces AKS to use `loadBalancer`, so egress can change instead of using the existing stable address. Add an explicit existing-network NAT/outbound setting or fail when stable egress is requested.</violation>
</file>
<file name="deployment/terraform/modules/azure/onyx/variables.tf">
<violation number="1" location="deployment/terraform/modules/azure/onyx/variables.tf:278">
P1: The default composition deploys a public AKS API server open to any address: private_cluster_enabled defaults to false and api_server_authorized_ip_ranges to [], and the aks module only forbids combining them, it never requires one. The module's own README lists this as a security requirement. Add a validation on private_cluster_enabled (or api_server_authorized_ip_ranges) that fails the plan unless one is set, instead of shipping an open control plane until a user reads the docs.</violation>
</file>
<file name="deployment/terraform/modules/azure/README.md">
<violation number="1" location="deployment/terraform/modules/azure/README.md:147">
P3: In the 'Using an existing network' example, postgres_password = var.postgres_password references a variable that nothing in this README defines; the quickstart just above uses a local (postgres_password = local.postgres_password). A reader copy-pasting both gets an undefined-variable error. Reference the local instead to match the established example.</violation>
<violation number="2" location="deployment/terraform/modules/azure/README.md:252">
P1: Configure the chart's file-store workloads to use the federated `onyx-workload-access` service account and add `azure.workload.identity/use: "true"` to their pod templates before relying on `DefaultAzureCredential`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
8c256a0 to
4d82f9a
Compare
There was a problem hiding this comment.
5 issues found across 6 files
Confidence score: 2/5
deployment/terraform/modules/azure/README.mddoes not require the Helm release to use theonyxnamespace, so the namespace-scopedonyx-workload-accessServiceAccount may not be available to the workload; install with--namespace onyx --create-namespace.deployment/terraform/modules/azure/onyx/main.tftruncates long normalized prefixes enough to retain only three digest characters, allowing distinct deployments to collide in Azure’s global storage-account namespace; reserve space for the full six-character digest.deployment/terraform/modules/azure/onyx/outputs.tfreports an emptynat_gateway_public_ipslist whencreate_virtual_network=false, which can produce incomplete consumer allowlists despite the supplied subnet using a NAT gateway; expose the relevant NAT gateway IPs in this path.deployment/terraform/modules/azure/onyx/variables.tfmisstates howenable_entra_database_authenticationaffects password logins, whiledeployment/terraform/modules/azure/tests/onyx.tftest.hclhas a tautological password assertion; correct the description and make the test verify the module’s behavior rather than its input.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="deployment/terraform/modules/azure/onyx/outputs.tf">
<violation number="1" location="deployment/terraform/modules/azure/onyx/outputs.tf:87">
P2: When create_virtual_network=false, nat_gateway_public_ips is always [] even though the cluster really does egress through the supplied subnet's NAT gateway, so consumers wiring allowlists off this output get an empty set. The output is accurate to what the module itself creates, but the description ('Egress addresses of the cluster') overstates it in the BYO-network path. Note the caveat in the output description, e.g. that it only reflects a NAT gateway the module created.</violation>
</file>
<file name="deployment/terraform/modules/azure/onyx/variables.tf">
<violation number="1" location="deployment/terraform/modules/azure/onyx/variables.tf:190">
P3: The postgres_password description says enable_entra_database_authentication turns password logins off, but that variable only adds Entra logins on top of passwords; entra_database_authentication_only is the one that disables them, and the validation checks that one. Fix the description so it names the correct variable and doesn't mislead users into skipping the password by setting enable_entra_database_authentication alone.</violation>
</file>
<file name="deployment/terraform/modules/azure/onyx/main.tf">
<violation number="1" location="deployment/terraform/modules/azure/onyx/main.tf:20">
P2: When the normalized name prefix reaches 18 characters, this truncation keeps only three of the six digest characters, so distinct deployments can collide in Azure's global storage-account namespace. Reserve 15 characters for the prefix so the full digest remains in the 24-character name.</violation>
</file>
<file name="deployment/terraform/modules/azure/onyx/tests/onyx.tftest.hcl">
<violation number="1" location="deployment/terraform/modules/azure/onyx/tests/onyx.tftest.hcl:273">
P3: This assertion is tautological: it only restates `postgres_password = null` from the run's own variables block, so it can never fail and verifies nothing about the module. The meaningful part of this test is that the plan succeeds without a password; drop the redundant assert, or replace it with one that checks a real signal from the module (e.g. `var.entra_database_authentication_only`).</violation>
</file>
<file name="deployment/terraform/modules/azure/README.md">
<violation number="1" location="deployment/terraform/modules/azure/README.md:273">
P1: Require the Helm release to be installed in the `onyx` namespace, for example with `--namespace onyx --create-namespace`. The Terraform-created `onyx-workload-access` ServiceAccount is namespace-scoped, so installing elsewhere makes the API and Celery pods reference a nonexistent account and fail to start.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
4d82f9a to
812cdb4
Compare
812cdb4 to
6e04fb5
Compare
| variable "create_workload_service_account" { | ||
| type = bool | ||
| description = "Create the workload service account. Turn this off when the Helm chart already creates it." | ||
| default = true |
There was a problem hiding this comment.
Service-account opt-out drops namespace
When a caller sets create_workload_service_account=false so Helm can create the service account while leaving create_workload_namespace=true, the AKS module also skips namespace creation. The documented Helm command does not pass --create-namespace, so installation fails because the onyx namespace does not exist.
Prompt To Fix With AI
This is a comment left during a code review.
Path: deployment/terraform/modules/azure/onyx/variables.tf
Line: 396-399
Comment:
**Service-account opt-out drops namespace**
When a caller sets `create_workload_service_account=false` so Helm can create the service account while leaving `create_workload_namespace=true`, the AKS module also skips namespace creation. The documented Helm command does not pass `--create-namespace`, so installation fails because the `onyx` namespace does not exist.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.6e04fb5 to
e45b86a
Compare
|
Preview Deployment
|
e45b86a to
1319be2
Compare
Wires the six modules together with t-shirt sizing, and documents the set. Completes the Azure equivalent of deployment/terraform/modules/aws. Sizing picks the Azure size closest to what the AWS composition chooses at each tier, with one deliberate departure: the index pool is memory-optimised at every tier, because on Azure it carries the document index itself. There is no managed OpenSearch to move that load off the cluster. Two things the composition does that no single module can: - Flow logs get a storage account of their own. That keeps the dependency one-way - log account, then network, then the file store account that restricts itself to the network's subnets - where pointing the network at the file store account would have made a cycle. - Egress picks itself. With a created network and a NAT gateway the cluster routes through it and keeps one address; bringing a network without one falls back to letting AKS manage outbound. Unlike the AWS composition this module declares no provider block. The azurerm provider needs a features block and a subscription, and both belong to the root module rather than to a module that might be counted. Flow logs are off by default, where the AWS modules have them on. Azure writes them to a storage account and needs a Network Watcher in the region, so a default of on would fail an apply on any subscription without one. The README covers turning them on.
1319be2 to
ff8a30e
Compare
Description
Wires the six modules together with t-shirt sizing, and documents the set. Completes the Azure equivalent of
deployment/terraform/modules/aws.Sizing picks the Azure size closest to what the AWS composition chooses at each tier, with one deliberate departure: the index pool is memory-optimised at every tier, because on Azure it carries the document index itself. There is no managed OpenSearch to move that load off the cluster.
Standard_D8ds_v5Standard_D16ds_v5Standard_D16ds_v5Standard_E4ds_v5Standard_E8ds_v5Standard_E16ds_v5GP_Standard_D2ds_v5GP_Standard_D2ds_v5GP_Standard_D4ds_v5Two things the composition does that no single module can:
Unlike the AWS composition this module declares no provider block. The
azurermprovider needs afeaturesblock and a subscription, and both belong to the root module rather than to a module that might be counted.One deliberate parity gap, for a decision
Flow logs are off by default, where the AWS modules have them on. Azure writes them to a storage account and needs a Network Watcher in the region, so a default of on would fail an apply on any subscription without one — and Azure only auto-creates the Watcher if the subscription has not opted out. I chose a working default over matching AWS, and the README documents the one variable that turns them on. Happy to flip it if you would rather fail loudly than ship less logging than AWS.
How Has This Been Tested?
All seven modules together: 85 passed, 0 failed.
The composition suite covers every sizing tier, per-variable overrides beating tier defaults without disturbing their neighbours, the derived storage account name staying inside Azure's 24-character lowercase-alphanumeric rule (including from a long prefix), the resource group create/join paths, and the flow-log account.
This suite caught a real defect in the
aksmodule that its own tests could not:azurerm_role_assignmentusedfor_eachover storage account IDs that are unknown at plan time when they come from a storage module in the same apply, which failsterraform planoutright on any greenfield deployment. Fixed in PR 5 of this stack.I also cross-checked the README against the code rather than trusting it: every
module.onyx.*output it references exists, and all fourAZURE_*environment variables it names are read bybackend/onyx/configs/app_configs.pyand exposed indeployment/helm/charts/onyx/values.yaml.Not applied against a live subscription. The README says so up front. Mocked plans prove the wiring and the input validation; only an apply proves the modules work.
Additional Options
Changes from review (greptile, cubic)
defaultservice account, soDefaultAzureCredentialgot no token and every file-store call failed to authenticate. Rewritten into three concrete steps: pointserviceAccount.nameat the federated account; add theazure.workload.identity/usepod label to the API server and every Celery worker (it is per-pod, and the webhook will not project a token without it); and give the OpenSearch subchart thenodeSelectorandtolerationsfor the tainted index pool. I checked the chart — it sets none of these, so the index pool would otherwise have sat empty while OpenSearch scheduled onto the system pool.entra_database_authentication_onlyis set, which the composition now supports end to end.aks_outbound_typelets a supplied subnet that already has a NAT gateway keep its stable egress address, instead of being forced ontoloadBalancer.var.postgres_passwordwhile the quickstart defines a local.Tests: 14 → 21. Across all seven modules: 120 passed, 0 failed (was 85).
Round 2
onyxnamespace. A service account belongs to one namespace, and the federated one is created inonyx. A release installed anywhere else references an account that does not exist there, and the API and Celery pods never start.log, so at a full-length prefix the 24-character truncation kept only three of the six digest characters — two deployments could collide in Azure's global namespace. Its prefix is now 15 characters, so15 + 3 + 6 = 24and the digest survives whole. The file store name was already fine at18 + 6.nat_gateway_public_ipssays what it actually reflects — a NAT gateway this module created, not egress in general.postgres_passworddescription names the right variable.enable_entra_database_authenticationonly adds Entra logins alongside passwords;entra_database_authentication_onlyis the one that removes the need for a password.Tests: 21 → 22. Across all seven modules: 137 passed, 0 failed (was 120).
Round 3
onyxnamespace before anything had made that namespace, and the apply failed. The Helm install could not have satisfied it either, since it runs afterwards.--create-namespace. Terraform owns the namespace now, so the release joins it.create_workload_namespaceis exposed here to opt out.Tests: 22. Across all seven modules: 140 passed, 0 failed.